﻿using System;
using System.Linq;
using System.ComponentModel.Composition;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Server;

namespace TfsRollUp.Core
{
    /// <summary>
    /// This plugin is called by Tfs for workitem and metadata changes. <see cref="SubscribedTypes"/>
    /// </summary>
    internal sealed class Plugin : ISubscriber
    {
        /// <summary>
        /// My name
        /// </summary>
        public string Name
        {
            get { return Constants.TfsRollUpPlugin; }
        }

        /// <summary>
        /// My priority
        /// </summary>
        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Normal; }
        }

        /// <summary>
        /// Send the root for this workitem to processing in <see cref="Service"/>
        /// </summary>
        /// <param name="requestContext">Tfs context</param>
        /// <param name="ev">The change event</param>
        /// <param name="workItemId">Id of workitem that has changed</param>
        private static void ProcessWorkItem(TeamFoundationRequestContext requestContext, WorkItemChangedEvent ev, int workItemId)
        {
            TeamFoundationTrace.MethodEntry(new object[] { workItemId });

            var rollUpService = requestContext.GetService<Service>();
            if (rollUpService == null)
            {
                TeamFoundationTrace.Error("{0}.ProcessWorkItem: rollUpService is null", Constants.TfsRollUpPlugin);
                return;
            }
            if (!rollUpService.Settings.EnableRollUpProcesing)
            {
                TeamFoundationTrace.Verbose("{0}.ProcessWorkItem: Processing has been disabled", Constants.TfsRollUpPlugin);
                return;
            }
            var store = rollUpService.WorkItemStore(requestContext);
            if (store == null)
            {
                TeamFoundationTrace.Error("{0}.ProcessWorkItem: store is null", Constants.TfsRollUpPlugin);
                return;
            }
            var workItem = store.GetWorkItem(workItemId);
            TeamFoundationTrace.Verbose("{0}.ProcessWorkItem: WorkItemId:{1} read", Constants.TfsRollUpPlugin, workItemId);
            if (ShouldProcessWorkItem(workItem, ev, rollUpService, store))
            {
                var fieldCollection = rollUpService.FieldCollection(requestContext);
                var fieldList = fieldCollection.GetRollUpFieldList((string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);
                if (fieldList != null && fieldList.Count() > 0)
                {
                    // Push the workitemstore uri, and the workitem id to the processing queue
                    rollUpService.WorkItemRootQueue.Add(new Tuple<Uri,int>(store.TeamProjectCollection.Uri, workItem.Root(fieldList)[0].Id));
                }
                else
                {
                    TeamFoundationTrace.Error("{0}.ProcessWorkItem: fieldList is null or count is 0", Constants.TfsRollUpPlugin);
                }
            }
            TeamFoundationTrace.MethodExit();
        }

        /// <summary>
        /// Filters the workitem; looks for changes and if relevant for rollup fields
        /// </summary>
        /// <param name="workItem">The item that has changed, see <see cref="WorkItem"/></param>
        /// <param name="ev">the event, see <see cref="WorkItemChangedEvent"/></param>
        /// <param name="rollUpService">our service</param>
        /// <param name="store">The workitem store, see <see cref="WorkItemStore"/></param>
        /// <returns>true if item should be processed, else false</returns>
        private static bool ShouldProcessWorkItem(WorkItem workItem, WorkItemChangedEvent ev, Service rollUpService, WorkItemStore store)
        {
            TeamFoundationTrace.MethodEntry();
            #region ValidateParameters
            if (workItem == null)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: workItem is null", Constants.TfsRollUpPlugin);
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (ev == null)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: ev is null", Constants.TfsRollUpPlugin);
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (rollUpService == null)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: rollUpService is null", Constants.TfsRollUpPlugin);
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (store == null)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: store is null", Constants.TfsRollUpPlugin);
                TeamFoundationTrace.MethodExit();
                return false;
            }
            #endregion

            if (workItem.RelatedLinkCount == 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: No links found on WorkItemId:{1}", Constants.TfsRollUpPlugin, workItem.Id);
                TeamFoundationTrace.MethodExit();
                return false;
            }

            if (ev.ChangerSid == rollUpService.SystemAccountSid)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: ChangerSid '{1}' is TfsServiceAccount, we advise against circular updates", Constants.TfsRollUpPlugin, workItem.ChangedBy);
                TeamFoundationTrace.MethodExit();
                return false;
            }

            var fieldCollection = rollUpService.FieldCollection(store.TeamProjectCollection.Uri);
            if (fieldCollection == null)
            {
                TeamFoundationTrace.Error("{0}.ShouldProcessWorkItem: unexpected null for fieldCollection for uri:{1}", Constants.TfsRollUpPlugin, store.TeamProjectCollection.Uri);
                TeamFoundationTrace.MethodExit();
                return false;
            }

            var fieldList = fieldCollection.GetRollUpFieldList((string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);
            if (fieldList == null || fieldList.Length == 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: RollUp fields for WorkItemId:{1} was not found in TfsCollection:'{2}' TeamProject:'{3}' WorkItemType:'{4}'", Constants.TfsRollUpPlugin, workItem.Id, store.TeamProjectCollection.Name, (string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);
                TeamFoundationTrace.MethodExit();
                return false;
            }

            if (ev.ChangedFields != null && ev.ChangedFields.StringFields != null)
            {
                // Look for changed fields that match a rollup fieldname
                foreach (var changedField in ev.ChangedFields.StringFields)
                {
                    if (changedField.NewValue != changedField.OldValue)
                    {
                        var matchFound = (from p in fieldList where p.Equals(changedField.ReferenceName) select p).SingleOrDefault();
                        if (!string.IsNullOrWhiteSpace(matchFound))
                        {
                            TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: Change found. Field:{1} OldValue:{2} NewValue:{3}", Constants.TfsRollUpPlugin, matchFound, changedField.OldValue, changedField.NewValue);
                            TeamFoundationTrace.MethodExit();
                            return true;
                        }
                    }
                }
            }

            if (ev.AddedRelations != null && ev.AddedRelations.Length > 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: Relations was added for WorkItemId:{1}", Constants.TfsRollUpPlugin, workItem.Id);
                TeamFoundationTrace.MethodExit();
                return true;
            }
            TeamFoundationTrace.MethodExit();
            return false;
        }

        /// <summary>
        /// Called by Tfs when a subscribed event happens
        /// </summary>
        /// <param name="requestContext">Tfs context, see <see cref="TeamFoundationRequestContext"/></param>
        /// <param name="notificationType">What happened, see <see cref="NotificationType"/></param>
        /// <param name="notificationEventArgs">Event arguments</param>
        /// <param name="statusCode">Status code to send back</param>
        /// <param name="statusMessage">Status message to send back</param>
        /// <param name="properties">Exception properties, see <see cref="ExceptionPropertyCollection"/></param>
        /// <returns>Status for the notification</returns>
        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
        {
            TeamFoundationTrace.MethodEntry();
            statusCode = 0;
            properties = null;
            statusMessage = String.Empty;
            if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemMetadataChangedNotification)
            {
                try
                {
                    var rollUpService = requestContext.GetService<Service>();
                    rollUpService.ServiceFlushCache();
                }
                catch (Exception ex)
                {
                    TeamFoundationApplication.LogException(string.Format("{0}.ProcessEvent: Error in workitemchangednotification", Constants.TfsRollUpPlugin), ex);
                }
            }
            else if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
            {
                WorkItemChangedEvent ev = notificationEventArgs as WorkItemChangedEvent;
                int workItemId = 0;
                try
                {
                    // Get the id of the work item that was just changed by the user.
                    workItemId = ev.CoreFields.IntegerFields[0].NewValue;
                    TeamFoundationTrace.Verbose("{0}.ProcessEvent: Processing workitemid:{1}", Constants.TfsRollUpPlugin, workItemId);
                    ProcessWorkItem(requestContext, ev, workItemId);
                }
                catch (Exception ex)
                {
                    TeamFoundationApplication.LogException(string.Format("{0}.ProcessEvent: Error processing workitem:{1}", Constants.TfsRollUpPlugin, workItemId), ex);
                }
            }
            TeamFoundationTrace.MethodExit();
            return EventNotificationStatus.ActionPermitted;
        }

        /// <summary>
        /// Events that Tfs should call this plugin for
        /// </summary>
        /// <returns>The types of events that we are interested in</returns>
        public Type[] SubscribedTypes()
        {
            return new Type[] { typeof(WorkItemChangedEvent), typeof(WorkItemMetadataChangedNotification) };
        }
    }
}
